Skip to content

Conversation

@hwennnn
Copy link
Contributor

@hwennnn hwennnn commented Feb 11, 2026

Summary

  • Implements a Dify plugin that exposes the Mino web automation API as 5 tools: run sync, run async, run SSE streaming, list runs, and get run by ID
  • Each tool maps directly to a Mino API endpoint with matching request/response schemas
  • Includes provider-level API key validation, proxy config support (7 country options), and browser profile selection (lite/stealth)

Implementation

Core logic (tools/)

File What it does
tools/run_sync.py Synchronous run — blocks until the automation finishes
tools/run_async.py Async run — returns a run_id immediately for polling
tools/run_sse.py SSE streaming run — streams progress events in real time
tools/get_run.py Get a run by ID (used to poll async runs)
tools/list_runs.py List past runs with pagination
tools/constants.py API base URL constant (1 line)

Boilerplate / config

File What it is
manifest.yaml Plugin metadata, resource limits, tool list
provider/tinyfish_web_agent.py API key validation (11 lines)
provider/tinyfish_web_agent.yaml Provider config schema (credentials UI)
tools/*.yaml Tool parameter definitions for the Dify UI (auto-generated-ish)
main.py Dify plugin entrypoint (6 lines)
.github/workflows/plugin-publish.yml CI to package & PR to Dify marketplace
GUIDE.md, README.md, PRIVACY.md, readme/ Docs & localized READMEs

Demo

Screen.Recording.2026-02-07.at.8.34.54.PM.mov
  • I use a LLM agent to extract goal and url from the user query. Since our API does not support raw query.
  • And inject the system prompt to run_async to execute the run and poll get-run for the status and the final result.

How to Install Dify

Test plan

  • Self-host Dify with Docker (the cloud version doesn't support custom plugins)
  • Package plugin with the Dify CLI
  • Install plugin and configure API key
  • Demo: run each tool end-to-end against a live site (demo recording above)

@hwennnn hwennnn self-assigned this Feb 11, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

This PR adds a TinyFish Web Agent plugin for Dify: provider and credential validation, a shared TinyfishMixin API client, five tools (RunSync, RunAsync, RunSSE, ListRuns, GetRun) with robust HTTP and error handling, manifest and provider YAMLs, docs/localization, CI workflow for packaging/publishing, example env and ignore files, a minimal plugin entrypoint, and a LICENSE. It also adds API_BASE_URL and a dify_plugin dependency.

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Dify Agent/Workflow
    participant Tool as Tool Implementation
    participant API as TinyFish Web Agent API
    participant Runtime as Dify Runtime

    rect rgba(100, 150, 255, 0.5)
    Note over Agent,Runtime: Synchronous Execution (RunSyncTool)
    Agent->>Tool: _invoke(url, goal, browser_profile, proxy_config)
    Tool->>Runtime: Retrieve api_key from credentials
    Tool->>Tool: Build automation payload
    Tool->>API: POST /v1/automation/run (timeout: 300s)
    API-->>Tool: Response with status & result
    Tool->>Tool: Parse status (COMPLETED/FAILED/OTHER)
    Tool-->>Agent: Emit result messages + JSON payload
    end

    rect rgba(100, 255, 150, 0.5)
    Note over Agent,Runtime: Asynchronous Execution (RunAsyncTool)
    Agent->>Tool: _invoke(url, goal, browser_profile, proxy_config)
    Tool->>Runtime: Retrieve api_key from credentials
    Tool->>Tool: Build automation payload
    Tool->>API: POST /v1/automation/run-async
    API-->>Tool: Response with run_id
    Tool-->>Agent: Emit run_id + guidance message
    end

    rect rgba(255, 200, 100, 0.5)
    Note over Agent,Runtime: Server-Sent Event Streaming (RunSseTool)
    Agent->>Tool: _invoke(url, goal, browser_profile, proxy_config)
    Tool->>Runtime: Retrieve api_key from credentials
    Tool->>Tool: Build automation payload
    Tool->>API: Open SSE stream to /v1/automation/run-sse
    loop Event Stream Processing
        API-->>Tool: Event (STARTED/STREAMING_URL/PROGRESS/COMPLETE)
        Tool->>Tool: Parse event data
        Tool-->>Agent: Emit corresponding message
    end
    Tool-->>Agent: Close stream + final JSON result
    end

    rect rgba(200, 100, 255, 0.5)
    Note over Agent,Runtime: Polling & Listing (GetRunTool / ListRunsTool)
    Agent->>Tool: _invoke(run_id or filters)
    Tool->>Runtime: Retrieve api_key from credentials
    Tool->>API: GET /v1/runs/{run_id} or GET /v1/runs?params
    API-->>Tool: Response with run data/status
    Tool->>Tool: Parse response (status, metadata)
    Tool-->>Agent: Emit status/list messages + JSON payload
    end
Loading
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: integrate tinyfish dify plugin' is concise and clearly describes the main change—integrating a Dify plugin for TinyFish web automation—which aligns with the substantial changes across manifest files, tool implementations, and configuration files.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset. It explains the core functionality (5 tools exposing the Mino API), implementation details with a file breakdown, testing evidence, and a demo recording—all aligning with the actual changes made.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/integrate-tinyfish-dify-plugin

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In `@dify/.env.example`:
- Around line 1-3: Reorder the keys in .env.example so they are alphabetically
ordered to satisfy dotenv-linter: change the sequence from INSTALL_METHOD,
REMOTE_INSTALL_URL, REMOTE_INSTALL_KEY to INSTALL_METHOD, REMOTE_INSTALL_KEY,
REMOTE_INSTALL_URL; update only the ordering of the three variables
(INSTALL_METHOD, REMOTE_INSTALL_KEY, REMOTE_INSTALL_URL) without changing their
values.

In `@dify/.github/workflows/plugin-publish.yml`:
- Around line 82-83: The branch name used when creating the branch
(BRANCH_NAME="bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{
steps.get_basic_info.outputs.version }}"; git checkout -b "$BRANCH_NAME") does
not match the branch referenced later when creating the PR; update either the
branch creation or the PR head to use the same convention: change the
BRANCH_NAME format to match the PR head (e.g., "${{
steps.get_basic_info.outputs.plugin_name }}-${{
steps.get_basic_info.outputs.version }}") or change the PR head to "bump-${{
steps.get_basic_info.outputs.plugin_name }}-plugin-${{
steps.get_basic_info.outputs.version }}", making sure all references to
BRANCH_NAME and the gh pr create head/head_ref use the identical string (use the
BRANCH_NAME variable to populate the PR head to avoid future mismatches).

In `@dify/GUIDE.md`:
- Around line 98-102: The fenced code block containing INSTALL_METHOD,
REMOTE_INSTALL_URL, and REMOTE_INSTALL_KEY needs a language specifier for syntax
highlighting and accessibility; change the opening triple backticks for that
block from ``` to ```bash so the block explicitly specifies bash/sh and renders
correctly.
- Line 115: The "Manual Packaging" heading currently uses four hashes (####
Manual Packaging) which jumps two levels from the surrounding h2; change that
heading to three hashes (### Manual Packaging) so the heading level increments
by a single level and matches the document structure.

In `@dify/README.md`:
- Line 22: The Markdown images lack alt text; update each image markdown (e.g.,
the line with "![](./_assets/authorization.png)" and the other image lines at
the same locations) to include concise, descriptive alt text inside the brackets
(for example replace "![](./_assets/authorization.png)" with "![Authorization
diagram](./_assets/authorization.png)"), and do the same for the images at lines
32, 63 and 81 so all four images have meaningful alt attributes.
- Line 14: Replace the incorrect indefinite article in the heading "Create an
TinyFish Web Agent API Key" by changing "an" to "a" so the header reads "Create
a TinyFish Web Agent API Key"; update the heading text in README.md (the line
containing "Create an TinyFish Web Agent API Key") accordingly.

In `@dify/readme/README_ja_JP.md`:
- Around line 1-3: Replace the placeholder Japanese README content under the "##
プラグイン Readme" header with a localized, meaningful description or a link to the
canonical README; specifically update the line currently reading
"ここに詳細なプラグイン説明ドキュメントを記載してください。" to provide either a full Japanese summary of the
plugin (purpose, usage, configuration, links) or a clear link to the main README
so the shipped package does not contain empty placeholder text.

In `@dify/readme/README_pt_BR.md`:
- Around line 1-3: The Portuguese README (README_pt_BR.md) is just a placeholder
and must be removed or replaced with a proper Brazilian Portuguese translation;
either delete README_pt_BR.md if localized docs aren't ready, or populate it by
translating the main README.md (and GUIDE.md where relevant) into clear
Brazilian Portuguese, preserving headings, code blocks, table of contents,
links, and any metadata so the translated file matches the original structure
and content.

In `@dify/readme/README_zh_Hans.md`:
- Around line 1-3: The README_zh_Hans.md currently contains a placeholder
(“请在这里填写详细的插件说明文档。”); replace that placeholder text with a proper Chinese
localization of the plugin README (detailing purpose, installation, usage,
config, and links) or, if translation isn’t ready, insert a clear link to the
canonical README and a note pointing to where localized docs will be added;
update the header and body in README_zh_Hans.md so no placeholder lines remain.

In `@dify/tools/run_sse.py`:
- Around line 71-93: The redundant end-of-run message occurs because
final_result is None for failed COMPLETE events but you still emit the generic
"Automation ended without returning a result" after handling COMPLETE; modify
the SSE handler to track completion explicitly (e.g., a boolean like
seen_complete or completed_flag) when event_type == "COMPLETE" and set it in
both the COMPLETED and non-COMPLETED branches, then only emit the trailing
create_text_message("Automation ended without returning a result") if
seen_complete is False (or if no COMPLETE was received) and final_result is
None; update references in this flow to event_type, status, final_result,
create_text_message and create_json_message so failed runs only show the failure
message and not the extra “ended without returning a result” message.
🧹 Nitpick comments (5)
dify/main.py (1)

1-6: Consider env-based configuration for MAX_REQUEST_TIMEOUT for operational flexibility.

The current hardcoded 120s is appropriate for SDK-level HTTP requests (e.g., OpenAI API calls); note that tool execution timeouts are separately configured (e.g., run_sse.py uses explicit 300.0s for SSE streams). Making this configurable via environment variable follows standard practice for containerized deployments.

⚙️ Optional env-based configuration
-from dify_plugin import Plugin, DifyPluginEnv
+import os
+from dify_plugin import Plugin, DifyPluginEnv
 
-plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
+timeout = int(os.getenv("DIFY_MAX_REQUEST_TIMEOUT", "120"))
+plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=timeout))
dify/tools/get_run.py (1)

40-64: Duplicate JSON output may confuse downstream consumers.

When the status is COMPLETED and result["result"] exists, two JSON messages are emitted: one with just the result payload (line 43) and another with the full response object (line 64). This could lead to redundant data in the output stream.

Consider whether the final create_json_message(result) at line 64 should be conditional (e.g., only emit if result wasn't already yielded), or clarify the intent with a comment if both are intentional for different consumers.

dify/.github/workflows/plugin-publish.yml (2)

14-24: CLI tool download lacks integrity verification.

The CLI binary is downloaded directly without checksum or signature verification. This is a supply chain security concern—if the download URL is compromised or MITM'd, malicious code could be executed in the workflow.

Consider adding checksum verification after download:

🔒 Proposed fix to add checksum verification
          wget https://github.com/langgenius/dify-plugin-daemon/releases/download/0.0.6/dify-plugin-linux-amd64
+         # Verify checksum (replace with actual sha256 from release)
+         echo "EXPECTED_SHA256_HERE  dify-plugin-linux-amd64" | sha256sum -c -
          chmod +x dify-plugin-linux-amd64

26-40: Manifest parsing is fragile and may fail on quoted or multi-space values.

The grep | cut approach assumes values have exactly one space after the colon and no quotes. Values like name: "my-plugin" or name: my-plugin (double space) will produce incorrect results.

Consider using yq for robust YAML parsing:

♻️ Proposed fix using yq for robust parsing
      - name: Get basic info from manifest
        id: get_basic_info
        run: |
-         PLUGIN_NAME=$(grep "^name:" manifest.yaml | cut -d' ' -f2)
+         PLUGIN_NAME=$(yq -r '.name' manifest.yaml)
          echo "Plugin name: $PLUGIN_NAME"
          echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT

-         VERSION=$(grep "^version:" manifest.yaml | cut -d' ' -f2)
+         VERSION=$(yq -r '.version' manifest.yaml)
          echo "Plugin version: $VERSION"
          echo "version=$VERSION" >> $GITHUB_OUTPUT

-         AUTHOR=$(grep "^author:" manifest.yaml | cut -d' ' -f2)
+         AUTHOR=$(yq -r '.author' manifest.yaml)
          echo "Plugin author: $AUTHOR"
          echo "author=$AUTHOR" >> $GITHUB_OUTPUT

Note: You may need to install yq first or use python -c "import yaml; ..." as an alternative.

dify/tools/run_async.yaml (1)

72-84: Minor: Inconsistent option labels compared to run_sse.yaml.

The browser profile options here use short labels ("Lite", "Stealth") while run_sse.yaml uses more descriptive labels ("Lite (Standard Browser)", "Stealth (Anti-Detection)"). Consider aligning these for a consistent user experience across tools.

♻️ Proposed fix to align labels with run_sse.yaml
    options:
      - value: "lite"
        label:
-         en_US: "Lite"
-         zh_Hans: "Lite"
-         pt_BR: "Lite"
-         ja_JP: "Lite"
+         en_US: "Lite (Standard Browser)"
+         zh_Hans: "Lite(标准浏览器)"
+         pt_BR: "Lite (Navegador Padrão)"
+         ja_JP: "Lite(標準ブラウザ)"
      - value: "stealth"
        label:
-         en_US: "Stealth"
-         zh_Hans: "Stealth"
-         pt_BR: "Stealth"
-         ja_JP: "Stealth"
+         en_US: "Stealth (Anti-Detection)"
+         zh_Hans: "Stealth(反检测)"
+         pt_BR: "Stealth (Anti-Detecção)"
+         ja_JP: "Stealth(検出防止)"

Comment on lines +82 to +83
BRANCH_NAME="bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{ steps.get_basic_info.outputs.version }}"
git checkout -b "$BRANCH_NAME"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Branch name mismatch will cause PR creation to fail.

The branch pushed at line 83 is named bump-{plugin_name}-plugin-{version}, but the PR head at line 103 references {plugin_name}-{version} (without the bump- prefix and -plugin suffix). This mismatch will cause the gh pr create command to fail with a "branch not found" error.

🐛 Proposed fix to align branch names
          gh pr create \
            --repo langgenius/dify-plugins \
-           --head "${{ steps.get_basic_info.outputs.author }}:${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}" \
+           --head "${{ steps.get_basic_info.outputs.author }}:bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{ steps.get_basic_info.outputs.version }}" \
            --base main \

Also applies to: 101-103

🤖 Prompt for AI Agents
In `@dify/.github/workflows/plugin-publish.yml` around lines 82 - 83, The branch
name used when creating the branch (BRANCH_NAME="bump-${{
steps.get_basic_info.outputs.plugin_name }}-plugin-${{
steps.get_basic_info.outputs.version }}"; git checkout -b "$BRANCH_NAME") does
not match the branch referenced later when creating the PR; update either the
branch creation or the PR head to use the same convention: change the
BRANCH_NAME format to match the PR head (e.g., "${{
steps.get_basic_info.outputs.plugin_name }}-${{
steps.get_basic_info.outputs.version }}") or change the PR head to "bump-${{
steps.get_basic_info.outputs.plugin_name }}-plugin-${{
steps.get_basic_info.outputs.version }}", making sure all references to
BRANCH_NAME and the gh pr create head/head_ref use the identical string (use the
BRANCH_NAME variable to populate the PR head to avoid future mismatches).

… and redirect the Portuguese version to the main README.
@hwennnn hwennnn merged commit 1bf4799 into main Feb 11, 2026
2 of 3 checks passed
@hwennnn hwennnn deleted the feat/integrate-tinyfish-dify-plugin branch February 11, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants